        title   SNDFRC -- Epoc/Os Sound Device Driver Using FRC
        subttl  Copyright (c) Psion PLC 1992
        name    SNDFRC
;
;  VER    DATE    BY  DESCRIPTION
; ----- -------- ---- -----------
; 1.00A 21/04/92 JH   Alpha release
;
TURBOC	equ	1
;
        include epocdef.inc
        include epocmac.inc
        include epocpan.inc
        include epoclib.inc
        include epocser.inc
        include ossibo.inc

HandlerDisable         equ     000h     ; the hnadler should not be called
HandlerEnable          equ     001h     ; the handler should be called

MAX_NOTES       equ     500

; This is allocated in the user space for the I/O channel
SndFrcEnt       struc
        SndFrcIo        ChanEnt <>      ; I/O channel hdr, MUST be first
        SndFrcWrite     RqEnt   <>
        SndFrcHandler   dw      ?
        SndFrcFlags     dw      ?
        SndFrcPstat     dw      ?
SndFrcEnt       ends

SndChannel      struc
        Pid             dw      ?       ; owning channel pid
        OpenFrcChan     db      ?       ; channel open status (KEEP ORDER)
        WriteStat       db      ?       ; write completion status (ORDER)
        DataPtr         dw      ?       ; where we are in buffer
        DataLength      dw      ?       ; number of notes to play
        DataPreBuffer   dw      ?       ; First Frc timout ptr
        DataBuffer      dw      MAX_NOTES dup (?)
SndChannel      ends

        CodeSeg

        ProcBegin@ SndfrcLDD
;                 ==========
; The externally loadable device table
        dw      LDDSignature
        db      'MUS',0,0,0,0,0
        dw      (VectorEnd-Vector)/2
Vector:
        dw      SndfrcInstall
        dw      SndfrcRemove
        dw      SndfrcHold
        dw      SndfrcResume
        dw      SndfrcReset
        dw      SndfrcUnits
        dw      SndfrcOpen
        dw      SndfrcStrategy
VectorHandler:
        dw      SndfrcHandler
VectorEnd:
        ProcEnd noret

SoundChan       SndChannel      <>

	ProcBegin@ disableFrcInt
;		   =============
; Disable the generation of Frc interrupts
;
	pushf
	cli
        in      al, A1InterruptMask
        and	al, not (mask FrcExpired)
        out     A1InterruptMask, al     ; stop Interrupt controller from
        popf                            ; generating Frc interrupts
	ret
	ProcEnd
;
        ProcBegin@ enableFrcInt
;		   ============
; Starts the free running counter.
;
	pushf
	cli
        in      al, A1Control
        or      al, mask FrcSource or mask FrcMode
        out     A1Control, al           ; set 512khz and prescale mode
        mov     ax, 5120                ; request a FrcInt every 10ms
	out	A1FrcControl, ax
        out     A1FrcEoi, al            ; clear any junk Frc Interrupt 
	in	al, A1InterruptMask
	or	al, mask FrcExpired     ; allow interrupt controller to
	out	A1InterruptMask, al     ; generate a Frc Interrupt
	popf
        ret
        ProcEnd noret
;

        ProcBegin@ FrcInterrupt,far
;                  ============
; The Frc interrupt routine.
; Although we could get addressability to the clients data space since it is
; possible that the operating system is currently moving the 
; data space of the client in which case the data buffer to be played
; may not be valid.
; The ISR is called by the operating system after all registers have been
; preserved.
; We MUST NOT cause a reschedule directly since the ISR would take a long
; time to complete if we did.
;
; Called in the context of whatever was running when the interrupt went off
;
        out     A1FrcEoi, al            ; clear Frc Interrupt 
        mov     si, SoundChan.DataPtr
        cmp     byte ptr cs:[si+1], 0   ; look at the duration byte 
        je      finishedThatNote        ; if zero goto the next note
        dec     byte ptr cs:[si+1]      ; reduce timeout
endFrcInterrupt:
	stc                             ; no potential reschedule required
endFrcInterruptFlags:
        out     A1NonSpecificEoi, al    ; finish the ISR
	ret

finishedThatNote:
        cmp     SoundChan.DataLength, 0
        je      finishedAllNotes
        dec     SoundChan.DataLength    ; one less note to play
        inc     si
        inc     si                      ; find next note,volume and duration
        mov     SoundChan.DataPtr, si   ; updated ptr 
        mov     al, byte ptr cs:[si]
        push    ax
        shr     ax, 1                   ; factor out the volume bits
        and     al, 60h                 ; into bits 5+6 for the hardware
        mov     ah, al
	mov	al, A2IWrite
	out	A2Index, al
	mov	al, ah
	out	A2Control, al           ; set the volume for that note
        pop     ax
	mov	dx, PSoundControl
        and     al, 3fh                 ; bottom 6 bits are note info
	out	dx, al                  ; and play that note
        jmp     short endFrcInterrupt
finishedAllNotes:
	call	StopSound
        mov     SoundChan.WriteStat, 0  ; finished playing that lot
        mov     bx, SoundChan.Pid       ; get the channel owner
        IoSignalByPidNoReSched          ; signal ourselves, causes handler
        clc                             ; reschedule if possible
        jmp     short endFrcInterruptFlags
	ProcEnd noret
;

	ProcBegin@ setFrcIntVector
;		   ===============
; Set the ISR pointer to our FRC interrupt routine.
; MUST be done with ints disabled so we cant be moved by the operating 
; system whilst setting the address.
;
        pushf
        cli
        push    bx
	mov	al, HwIrq5Revector
	mov	bx, offset FrcInterrupt
	mov	cx, cs          ; this ok as ints disabled.
	GenSetRevector
        pop     bx
        popf
        ret
        ProcEnd noret
;

	ProcBegin@ StopSound
;		   =========
; Stop the sound generation. 
;
        call    disableFrcInt           ; Stop Frc interrupts
	mov	dx, PSoundControl
	mov	al, 1
	out	dx, al                  ; stop sound chip
	HwComboOff                      ; stop amplifier
	ret
	ProcEnd noret

        ProcBegin@ SndfrcInstall,far
;                  =============
; Install vector called when application calls p_loadldd()
; Set OpenFrcChan to zero.
; In:
;       NONE
; Out:
;       Carry clear if installed ok
;       Carry set if failed to install
;
        and     SoundChan.OpenFrcChan, 0
        clc                             ; installed OK
        ret
        ProcEnd noret
;

        ProcBegin@ SndfrcRemove,far
;                  ============
; Remove vector called when application calls p_devdel()
;
        cmp     SoundChan.OpenFrcChan, 0
        je      soundRemoved
        mov     al, InUseErr
        stc                     ; channel open, fail to remove
        ret
        ProcEnd noret
; 

        ProcBegin@ SndfrcReset,far
;                  ===========
; Reset vector called if the client terminated.
; We can only get a reset if weve got channel open and client died
; since we cancel the reset request when the channel is closed.
; NOTE: The clients data space no longer exists thus all data required to
; close down the channel must be avaliable in the drivers space.
; In:
;       NONE
; Out:
;       NONE
;
        call    StopSound       ; disable Frc ints etc
	mov	al, HwIrq5Revector
	GenResetRevector        ; restore the default ROM ISR vector
	mov	al, mask FrcExpired
	HwFreeChannel           ; free Frc resource 
        HwFreeCombo             ; free up sound usage resource
        and     word ptr SoundChan.OpenFrcChan, 0  ; all closed now
soundRemoved:
        clc                     ; removed OK.
        ret
        ProcEnd noret


        ProcBegin@ SndfrcHold,far
;                  ==========
; Hold called when another driver loaded or switched off/power fail.
; In:
;       AH - reason.
; Out:
;       NONE

        cmp     SoundChan.OpenFrcChan, 0
        je      soundHeld               ; dont do anything if not open
        call    StopSound               ; shut down ints and sound
soundHeld:
        ret
        ProcEnd noret

        ProcBegin@ SndfrcResume,far
;                  ============
; Resume called after a Hold to allow the driver to continue.
; If we were running enabling the Frc interrupt will cause the 
; FrcInt routine to be called thus allowing us to count down the current
; duration as though nothing had happened before going to the next note.
; In:
;       NONE
; Out:
;       NONE
;
        cmp     SoundChan.OpenFrcChan, 0
        je      soundResumed            ; dont do anything if not open
        call    setFrcIntVector         ; we may have moved, set CS correctly
        cmp     SoundChan.WriteStat, PendingErr
        jne     soundResumed            ; dont enable ints if no write queued
	HwComboOn                       ; switch amplifier on
        call    enableFrcInt            ; continue where we left off
soundResumed:
        ret
        ProcEnd noret

        ProcBegin@ SndfrcUnits,far
;                  ===========
; We can only handle 1 sound channel at a time.
;
        mov     ax, 1                  ; support 1 unit at a time
        ret
        ProcEnd noret

        ProcBegin@ SndfrcOpen,far
;                  ==========
; Open the sound device driver.
; This runs is the context of the process that has called p_open("MUF:").
;
; In:
;       DS=ES=SS - Process data space.
;       DX      - OS device handle
;       SI      - ptr to OpenEnt struct
; Out:
;       DX      - OS device handle
;   Carry Clear
;       BX      - channel, DX is as passed (device handle)
;   Carry Set
;       AL      - error number
;
; All device drivers MUST have a ChanEnt as first item at returned BX
;
	HwGetCombo              ; see if snd already in use, if not grab it
	jc	sndAlreadyInUse
	mov	al, mask FrcExpired
	HwGetChannel            ; see if FRC already in use, if not grab it
        jc      endFrcInUse
        mov     cx, (size SndFrcEnt)
        HeapAllocateCell
        jc      endOpenNoMemory         ; get users I/O channel 
	mov	bx, ax                  ; the allocated cell handle
	mov	al, (VectorHandler-Vector)/2
	IoAddHandler                    ; handler handle in AX
        jc      endOpenFreeMemory
	mov	[bx].SndFrcHandler, ax
        xchg    bx, dx
        xor     cx, cx
        IoRequestReset                  ; call Reset if client terminates
        xchg    bx, dx
	mov	[bx].SndFrcIo.ChanNext, bx      ; we are a root device
	mov	[bx].SndFrcIo.ChanSignature, IoChanSignature
	mov	[bx].SndFrcIo.ChanLibHandle, dx ; This is our device handle
        and     [bx].SndFrcPstat, 0     ; no write pending yet
        and     [bx].SndFrcFlags, 0
        ProcId
        mov     SoundChan.Pid, ax       ; remember client id for ISR
        mov     SoundChan.OpenFrcChan, 1           ; now open
        call    setFrcIntVector         ; set up the ISR address ptr
        clc
        ret

endOpenFreeMemory:
        push    ax
        HeapFreeCell
        pop     ax
endOpenNoMemory:
        push    ax
	mov	al, mask FrcExpired
	HwFreeChannel           ; free Frc resource 
        pop     ax
endFrcInUse:
        push    ax
        HwFreeCombo             ; free sound resource as failed somewhere
        pop     ax
        stc
sndAlreadyInUse:
        ret
        ProcEnd noret

        ProcBegin@ SndfrcHandler,far
;                  ==============
; Called when an I/O signal is generated and were enabled. We should only
; enabled when a write request is outstanding.
; We need to check the LOW side drivers completion status to see if all the
; notes have been played yet (SoundChan.WriteStat!=E_FILE_PENDING). 
; Typically write the handler defensivly since complex drivers may leave the
; handler enabled other than when a request is outstanding.
;
; In:
;       DS,ES,SS == Process data space
;       BX == channel control block from open request
; Out:
;       Carry clear
;           Did not consume signal
;       Carry set
;           al = 0 - Leave handler disabled
;           al = non 0 - Enable handler
;
        test    [bx].SndFrcFlags, RQ_DISABLE_HANDLER
        jnz     signalNotForUs  ; we are in StategyVector code
        cmp     [bx].SndFrcPstat, 0
        je      signalNotForUs  ; no write request queued yet
        mov     al, SoundChan.WriteStat
        cmp     al, PendingErr
        je      signalNotForUs  ; pending write request not finished
        cbw
        xor     si, si
        xchg    si, [bx].SndFrcPstat    ; no write queued if Pstat is zero
        mov     [si], ax
        IoSignal                ; complete the original write request
        xor     ax, ax          ; disable handler now
        stc
        ret
signalNotForUs:
        clc
        ret
        ProcEnd noret

        ProcBegin@ CancelWrite
;                  ===========
; Cancel any outstanding write request
;
        xor     di, di
        xchg    di, [bx].SndFrcPstat
        or      di, di          ; see if any write is currently queued
        jz      noWriteQueued
        push    bx
        mov     bx, [bx].SndFrcHandler
        mov     cl, HandlerDisable
        IoEnableHandler         ; make SndfrcHandler non callable
        pop     bx
        call    StopSound       ; stops Frc ints
        mov     word ptr [di], CancelErr        ; set write completion stat
        IoSignal                ; and signal write completion
noWriteQueued:
        mov     SoundChan.WriteStat, 0
        ret
        ProcEnd noret
;

        ProcBegin@ SndfrcStrategy,far
;                  ==============
; Strategy vector called by an I/O request on the opened sound device driver
; channel.
; The wait handler may be called (when enabled ) unless we block it when we 
; call a sync I/O request. Allowing the waithandler to run whilst in the 
; strategy vector is generally not a good thing to do.
; In this particular example there are infact no sync calls to the I/O system
; thus the wait handler cannot be run. In more complex drivers it may be
; difficult to determine all possible paths through the code thus the driver
; should be written defensivly (use RQ_DISABLE_HANDLER type mechanism).
; In:
;       DS,ES,SS == Process data space
;       BX == channel control block from open request
;       DX == device handle
;       SI == RqEnt pointer
; Out:
;       Carry clear
;           - request queued sucessfully (may have completed)
;       Carry set
;           - error queuing the I/O request
;
	cld
        or      [bx].SndFrcFlags, RQ_DISABLE_HANDLER
	mov	al, byte ptr [si].RqFunction
	cmp	al, IoFuncWrite
	jne	tryCancel
        cmp     [bx].SndFrcPstat, 0
        jne     writeQueued             ; panic if write already queued
        mov     di, [si].RqA2Ptr
        mov     cx, [di]                ; length to write
        cmp     cx, MAX_NOTES
        jae     writeQueued             ; length too much, panic dvr
        mov     di, [si].RqStatusPtr
        mov     word ptr [di], PendingErr
        mov     [bx].SndFrcPstat, di    ; write request now queued
        mov     si, [si].RqA1Ptr        ; ptr to data to write as sound
        mov     di, offset SoundChan.DataPreBuffer
        and     word ptr [di], 0        ; set pre buffer len to zero
        mov     SoundChan.DataPtr, di
        inc     di
        inc     di
        mov     SoundChan.DataLength, cx        ; this many notes to play
        mov     SoundChan.WriteStat, PendingErr
        pushf
        cli                             ; we can set ES when ints are off
        push    cs
        pop     es                      ; copy the notes+vol and duration
        popf                            ; into buf we can access in ISR
        rep     movsw                   ; dont use CS override
        mov     es, [bp].IntES
        push    bx
        mov     bx, [bx].SndFrcHandler
        mov     cl, HandlerEnable
        IoEnableHandler                 ; allow waithandler to be called
        pop     bx
	HwComboOn
        call    enableFrcInt            ; start the sound off
        and     [bx].SndFrcFlags, NOT RQ_DISABLE_HANDLER
        xor     ax, ax                  ; queued OK
        ret
writeQueued:
        mov     al, PanicIoPending
        ProcPanic                       ; stop bad applications

tryCancel:
	cmp	al, IoFuncCancel
        jne     tryClose
        call    CancelWrite             ; stop any queued write 
        and     [bx].IoRequestFlags, NOT RQ_DISABLE_HANDLER
        jmp     short signalOk          ; cancel completed ok.
tryClose:
	cmp	al, IoFuncClose
        jne     notForUs
        call    CancelWrite             ; stop any queued writes
        push    bx
        push    [bx].SndFrcHandler
        push    dx
	mov	al, HwIrq5Revector
	GenResetRevector
        pop     bx              ; channel handle
	xor	cx, cx
	IoRequestResetCancel    ; dont call Reset vector now
        pop     bx              ; handler handle
        IoRemoveHandler
        pop     bx              ; alloc handle
        HeapFreeCell
	mov	al, mask FrcExpired
	HwFreeChannel           ; free Frc resource 
        HwFreeCombo             ; not in use any more
        and     word ptr SoundChan.OpenFrcChan, 0  ; all closed now
signalOk:
	xor	ax, ax
	mov	di, [si].RqStatusPtr
	stosw                   ; the I/O request completed OK
	IoSignal
	xor	ax, ax          ; also clears carry
	ret
notForUs:
        and     [bx].IoRequestFlags, NOT RQ_DISABLE_HANDLER
        IoRoot                  ; pass on request as not for us
        ret
        ProcEnd noret

        EndCodeSeg
;
stack   segment stack para 'data'
;
stack   ends
;
        end     SndfrcLDD

